# test_config.py
# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors
#
# This module is part of GitPython and is released under
# the BSD License: http://www.opensource.org/licenses/bsd-license.php

from git.test.lib import (
    TestCase,
    fixture_path,
    assert_equal,
)
from gitdb.test.lib import with_rw_directory
from git import (
    GitConfigParser
)
from git.compat import (
    string_types,
)
import io
import os
from git.config import cp


class TestBase(TestCase):

    def _to_memcache(self, file_path):
        fp = open(file_path, "rb")
        sio = io.BytesIO(fp.read())
        sio.name = file_path
        return sio

    def test_read_write(self):
        # writer must create the exact same file as the one read before
        for filename in ("git_config", "git_config_global"):
            file_obj = self._to_memcache(fixture_path(filename))
            w_config = GitConfigParser(file_obj, read_only=False)
            w_config.read()                 # enforce reading
            assert w_config._sections
            w_config.write()                # enforce writing

            # we stripped lines when reading, so the results differ
            assert file_obj.getvalue()
            self.assertEqual(file_obj.getvalue(), self._to_memcache(fixture_path(filename)).getvalue())

            # creating an additional config writer must fail due to exclusive access
            self.failUnlessRaises(IOError, GitConfigParser, file_obj, read_only=False)

            # should still have a lock and be able to make changes
            assert w_config._lock._has_lock()

            # changes should be written right away
            sname = "my_section"
            oname = "mykey"
            val = "myvalue"
            w_config.add_section(sname)
            assert w_config.has_section(sname)
            w_config.set(sname, oname, val)
            assert w_config.has_option(sname, oname)
            assert w_config.get(sname, oname) == val

            sname_new = "new_section"
            oname_new = "new_key"
            ival = 10
            w_config.set_value(sname_new, oname_new, ival)
            assert w_config.get_value(sname_new, oname_new) == ival

            file_obj.seek(0)
            r_config = GitConfigParser(file_obj, read_only=True)
            assert r_config.has_section(sname)
            assert r_config.has_option(sname, oname)
            assert r_config.get(sname, oname) == val
            w_config.release()
        # END for each filename

    @with_rw_directory
    def test_lock_reentry(self, rw_dir):
        fpl = os.path.join(rw_dir, 'l')
        gcp = GitConfigParser(fpl, read_only=False)
        with gcp as cw:
            cw.set_value('include', 'some_value', 'a')
        # entering again locks the file again...
        with gcp as cw:
            cw.set_value('include', 'some_other_value', 'b')
            # ...so creating an additional config writer must fail due to exclusive access
            self.failUnlessRaises(IOError, GitConfigParser, fpl, read_only=False)
        # but work when the lock is removed
        with GitConfigParser(fpl, read_only=False):
            assert os.path.exists(fpl)
            # reentering with an existing lock must fail due to exclusive access
            self.failUnlessRaises(IOError, gcp.__enter__)

    def test_multi_line_config(self):
        file_obj = self._to_memcache(fixture_path("git_config_with_comments"))
        config = GitConfigParser(file_obj, read_only=False)
        ev = "ruby -e '\n"
        ev += "		system %(git), %(merge-file), %(--marker-size=%L), %(%A), %(%O), %(%B)\n"
        ev += "		b = File.read(%(%A))\n"
        ev += "		b.sub!(/^<+ .*\\nActiveRecord::Schema\\.define.:version => (\\d+). do\\n=+\\nActiveRecord::Schema\\."
        ev += "define.:version => (\\d+). do\\n>+ .*/) do\n"
        ev += "		  %(ActiveRecord::Schema.define(:version => #{[$1, $2].max}) do)\n"
        ev += "		end\n"
        ev += "		File.open(%(%A), %(w)) {|f| f.write(b)}\n"
        ev += "		exit 1 if b.include?(%(<)*%L)'"
        assert_equal(config.get('merge "railsschema"', 'driver'), ev)
        assert_equal(config.get('alias', 'lg'),
                     "log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr)%Creset'"
                     " --abbrev-commit --date=relative")
        assert len(config.sections()) == 23

    def test_base(self):
        path_repo = fixture_path("git_config")
        path_global = fixture_path("git_config_global")
        r_config = GitConfigParser([path_repo, path_global], read_only=True)
        assert r_config.read_only
        num_sections = 0
        num_options = 0

        # test reader methods
        assert r_config._is_initialized is False
        for section in r_config.sections():
            num_sections += 1
            for option in r_config.options(section):
                num_options += 1
                val = r_config.get(section, option)
                val_typed = r_config.get_value(section, option)
                assert isinstance(val_typed, (bool, int, float, ) + string_types)
                assert val
                assert "\n" not in option
                assert "\n" not in val

                # writing must fail
                self.failUnlessRaises(IOError, r_config.set, section, option, None)
                self.failUnlessRaises(IOError, r_config.remove_option, section, option)
            # END for each option
            self.failUnlessRaises(IOError, r_config.remove_section, section)
        # END for each section
        assert num_sections and num_options
        assert r_config._is_initialized is True

        # get value which doesnt exist, with default
        default = "my default value"
        assert r_config.get_value("doesnt", "exist", default) == default

        # it raises if there is no default though
        self.failUnlessRaises(cp.NoSectionError, r_config.get_value, "doesnt", "exist")

    @with_rw_directory
    def test_config_include(self, rw_dir):
        def write_test_value(cw, value):
            cw.set_value(value, 'value', value)
        # end

        def check_test_value(cr, value):
            assert cr.get_value(value, 'value') == value
        # end

        # PREPARE CONFIG FILE A
        fpa = os.path.join(rw_dir, 'a')
        with GitConfigParser(fpa, read_only=False) as cw:
            write_test_value(cw, 'a')

            fpb = os.path.join(rw_dir, 'b')
            fpc = os.path.join(rw_dir, 'c')
            cw.set_value('include', 'relative_path_b', 'b')
            cw.set_value('include', 'doesntexist', 'foobar')
            cw.set_value('include', 'relative_cycle_a_a', 'a')
            cw.set_value('include', 'absolute_cycle_a_a', fpa)
        assert os.path.exists(fpa)

        # PREPARE CONFIG FILE B
        with GitConfigParser(fpb, read_only=False) as cw:
            write_test_value(cw, 'b')
            cw.set_value('include', 'relative_cycle_b_a', 'a')
            cw.set_value('include', 'absolute_cycle_b_a', fpa)
            cw.set_value('include', 'relative_path_c', 'c')
            cw.set_value('include', 'absolute_path_c', fpc)

        # PREPARE CONFIG FILE C
        with GitConfigParser(fpc, read_only=False) as cw:
            write_test_value(cw, 'c')

        with GitConfigParser(fpa, read_only=True) as cr:
            for tv in ('a', 'b', 'c'):
                check_test_value(cr, tv)
            # end for each test to verify
            assert len(cr.items('include')) == 8, "Expected all include sections to be merged"

        # test writable config writers - assure write-back doesn't involve includes
        with GitConfigParser(fpa, read_only=False, merge_includes=True) as cw:
            tv = 'x'
            write_test_value(cw, tv)

        with GitConfigParser(fpa, read_only=True) as cr:
            self.failUnlessRaises(cp.NoSectionError, check_test_value, cr, tv)

        # But can make it skip includes alltogether, and thus allow write-backs
        with GitConfigParser(fpa, read_only=False, merge_includes=False) as cw:
            write_test_value(cw, tv)

        with GitConfigParser(fpa, read_only=True) as cr:
            check_test_value(cr, tv)

    def test_rename(self):
        file_obj = self._to_memcache(fixture_path('git_config'))
        cw = GitConfigParser(file_obj, read_only=False, merge_includes=False)

        self.failUnlessRaises(ValueError, cw.rename_section, "doesntexist", "foo")
        self.failUnlessRaises(ValueError, cw.rename_section, "core", "include")

        nn = "bee"
        assert cw.rename_section('core', nn) is cw
        assert not cw.has_section('core')
        assert len(cw.items(nn)) == 4
        cw.release()

    def test_complex_aliases(self):
        file_obj = self._to_memcache(fixture_path('.gitconfig'))
        w_config = GitConfigParser(file_obj, read_only=False)
        self.assertEqual(w_config.get('alias', 'rbi'), '"!g() { git rebase -i origin/${1:-master} ; } ; g"')
        w_config.release()
        self.assertEqual(file_obj.getvalue(), self._to_memcache(fixture_path('.gitconfig')).getvalue())

    def test_empty_config_value(self):
        cr = GitConfigParser(fixture_path('git_config_with_empty_value'), read_only=True)

        assert cr.get_value('core', 'filemode'), "Should read keys with values"

        self.failUnlessRaises(cp.NoOptionError, cr.get_value, 'color', 'ui')
